package com.sp.fresh_produce.controller;

import com.sp.fresh_produce.common.Constant;
import com.sp.fresh_produce.common.GenericReturnApiObject;
import com.sp.fresh_produce.controller.dto.req.ProductRequest;
import com.sp.fresh_produce.controller.dto.req.ProductUpdateRequest;
import com.sp.fresh_produce.ex.CustomException;
import com.sp.fresh_produce.ex.ExceptionCodeEnum;
import com.sp.fresh_produce.model.pojo.Product;
import com.sp.fresh_produce.service.AdminProduceService;
import com.sp.fresh_produce.web.AdminRequired;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;

/**
 * 商品控制器-管理员
 * 
 * 该控制器负责处理管理员相关的商品管理请求，包括：
 * - 添加新的商品信息
 * - 上传商品图片
 * 
 * 通过 @RestController 注解，该类被注册为一个 RESTful Web 服务控制器，
 * 所有方法的返回值将自动转换为 JSON 格式响应给客户端。
 * 
 * 通过 @RequestMapping("/admin") 注解，所有该控制器下的接口路径都以 "/admin" 开头。
 */
@RestController
@RequestMapping("/admin")
@Tag(name = "AdminProduct", description = "管理员商品接口")
public class AdminProductController {

    private static final Logger log = LoggerFactory.getLogger(AdminProductController.class);

    /**
     * 管理员商品服务接口
     * 
     * 该字段通过 @Autowired 注解进行依赖注入，用于调用业务逻辑层的服务方法。
     * 具体的业务逻辑（如商品数据的持久化）由 AdminProducetService 实现。
     */
    @Autowired
    private AdminProduceService adminProductService;

    /**
     * 添加商品信息
     * 
     * 该方法接收一个包含商品详细信息的 ProductRequest 对象，并将其保存到数据库中。
     * 
     * @param productRequest 包含商品名称、价格、描述、分类等信息的请求对象
     *                       该对象会被 Spring MVC 自动从 HTTP 请求体中解析并绑定。
     *                       使用 @Valid 注解确保请求数据符合预定义的验证规则。
     *                       如果验证失败，Spring 将自动抛出 MethodArgumentNotValidException 异常。
     * 
     * @return GenericReturnApiObject 类型的响应对象
     *         - 如果商品添加成功，返回 success 状态码和 null 数据。
     *         - 如果商品添加失败（例如数据库操作异常），返回 fail 状态码和相应的错误信息。
     * 
     * @throws CustomException 当商品添加过程中发生业务逻辑异常时抛出，例如数据库操作失败。
     *                         该异常会被全局异常处理器捕获并统一处理。
     */
    @AdminRequired
    @Operation(summary = "添加商品信息")
    @PostMapping("/addProduct")
    public GenericReturnApiObject addProduct(@Valid @RequestBody ProductRequest productRequest) {
        // 调用服务层的 addProduct 方法执行商品添加逻辑
        // 该方法会将 productRequest 中的数据插入到数据库的商品表中
        int result = adminProductService.addProduct(productRequest);
        
        // 检查数据库操作结果
        // MyBatis 的 insert 操作返回值表示受影响的行数
        // 正常插入时返回值应为 1，插入失败时返回值为 0
        // 因此，使用 result < 0 来判断是否失败是不准确的，应该使用 result <= 0
        if (result <= 0) {
            // 如果插入失败，返回失败响应
            return GenericReturnApiObject.fail(ExceptionCodeEnum.PRODUCT_ADD_ERROR.getCode(), ExceptionCodeEnum.PRODUCT_ADD_ERROR.getMessage());
        }
        // 如果插入成功，返回成功响应
        return GenericReturnApiObject.success(null);
    }

    /**
     * 图片上传
     * 
     * 该方法处理前端上传的图片文件，将其保存到服务器指定目录，并返回图片的访问 URL。
     * 
     * @param httpServletRequest 当前的 HTTP 请求对象，用于获取请求的上下文信息，如请求 URI。
     *                           这些信息对于构建图片的访问路径至关重要。
     * @param file 待上传的 MultipartFile 对象，包含了文件的原始数据、名称、大小等信息。
     *             该对象由 Spring MVC 自动从 multipart/form-data 请求中解析得到。
     * 
     * @return GenericReturnApiObject 类型的响应对象
     *         - 如果文件上传成功，返回 success 状态码和图片的完整访问 URL。
     *         - 如果文件上传失败（例如文件夹创建失败或文件写入失败），返回 fail 状态码和相应的错误信息。
     * 
     * @throws URISyntaxException 如果在解析请求 URI 时发生异常，例如 URI 格式不正确。
     *                          该异常会在 getHost 方法中被捕获并处理。
     * @throws CustomException 当文件夹创建失败时抛出，表示文件系统操作异常。
     *                        该异常会被全局异常处理器捕获并统一处理。
     */
    @Operation(summary = "上传图片")
    @PostMapping("/uploadImage")
    public GenericReturnApiObject uploadImage(HttpServletRequest httpServletRequest, @RequestParam("file") MultipartFile file) throws URISyntaxException {
        // 获取文件名
        String fileName = file.getOriginalFilename();
        
        // 获取文件后缀名（扩展名）
        // 使用 lastIndexOf(".") 找到最后一个点的位置，然后截取后面的字符串
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        
        // 生成新的 UUID 作为文件名，避免文件名冲突
        // UUID.randomUUID().toString() 生成一个唯一的字符串
        // 将其与原文件的后缀名拼接，形成新的文件名
        String newFileName = UUID.randomUUID().toString() + suffixName;
        
        // 文件夹目录
        // Constant.FILE_UPLOAD_DIR 是一个静态常量，定义了文件上传的根目录
        File fileDirectory = new File(Constant.FILE_UPLOAD_DIR);
        
        // 文件
        // 构造目标文件的完整路径（使用目录对象拼接，避免缺少路径分隔符）
        File destFile = new File(fileDirectory, newFileName);
        
        // 文件夹不存在
        // 检查上传目录是否存在
        if (!fileDirectory.exists()) {
            // 文件夹创建失败
            // 如果目录不存在，则尝试递归创建所有父目录
            // mkdirs() 方法会创建多级目录
            if (!fileDirectory.mkdirs()) {
                // 如果创建失败，抛出自定义异常
                throw new CustomException(ExceptionCodeEnum.FILE_MKDIR_ERROR.getCode(), ExceptionCodeEnum.FILE_MKDIR_ERROR.getMessage());
            }
        }
        
        try {
            // 文件上传
            // 使用 transferTo 方法将 MultipartFile 的内容写入到目标文件中
            // 该方法会处理文件流的读取和写入
            file.transferTo(destFile);
        } catch (Exception e) {
            // 如果文件上传过程中发生任何异常（如磁盘空间不足、权限问题等）
            log.error("Image upload failed. Target path: {}", destFile.getAbsolutePath(), e);
            // 返回失败响应，包含错误码和错误信息
            return GenericReturnApiObject.fail(ExceptionCodeEnum.FILE_UPLOAD_ERROR.getCode(), ExceptionCodeEnum.FILE_UPLOAD_ERROR.getMessage());
        }
        
        // 构建图片的访问 URL
        // 使用 getHost 方法获取当前应用的主机地址和端口
        // 然后拼接上相对路径 "/images/" 和新生成的文件名
        // 最终返回一个完整的、可访问的图片 URL 给前端
        return GenericReturnApiObject.success(getHost(new URI(httpServletRequest.getRequestURI())).toString() + "/images/" + newFileName);
    }

    /**
     * 获取主机地址
     * 
     * 该私有方法用于从给定的 URI 中提取出主机地址（scheme, host, port）信息，
     * 并构建一个新的 URI，用于生成图片的访问链接。
     * 
     * @param uri 输入的 URI 对象，通常是从 HttpServletRequest 中获取的请求 URI。
     *            该 URI 可能包含路径、查询参数等额外信息。
     * 
     * @return URI 类型的对象，只包含 scheme, userInfo, host, port 信息，其余部分为空。
     *         如果解析过程中发生异常，返回 null。
     * 
     * @throws Exception 在构造新的 URI 时可能发生的各种异常，如 IllegalArgumentException。
     *                   该异常会被 catch 块捕获，导致返回 null。
     */
    private URI getHost(URI uri){
        URI effectiveURI;
        try {
            // 使用 URI 构造函数重新构建一个只包含基础信息的 URI
            // 参数依次为：scheme, userInfo, host, port, path, query, fragment
            // 这里将 path, query, fragment 设置为 null，以移除它们
            effectiveURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(),
                    uri.getPort(), null, null, null);
        } catch (Exception e) {
            // 如果构造失败，返回 null
            effectiveURI = null;
        }
        return effectiveURI;
    }


    /**
     *  修改商品信息
     */
    @AdminRequired
    @Operation(summary = "修改商品信息")
    @PutMapping( "/updateProduct")
    public GenericReturnApiObject updateProduct(@Valid @RequestBody ProductUpdateRequest productUpdateRequest) {
        Product product = new Product();
        BeanUtils.copyProperties(productUpdateRequest, product);
        adminProductService.updateProduct(product);
        return GenericReturnApiObject.success(null);
    }

    /**
     *  删除商品
     */
    @AdminRequired
    @Operation(summary = "删除商品")
    @DeleteMapping("/deleteProduct")
    public GenericReturnApiObject deleteProduct(@RequestParam Integer id) {
        adminProductService.deleteProduct(id);
        return GenericReturnApiObject.success(null);
    }

    /**
     *  批量上下架商品
     */
    @AdminRequired
    @Operation(summary = "批量上下架商品")
    @PostMapping("/batchUpdateProductStatus")
    public GenericReturnApiObject batchUpdateProductSellStatus(@RequestParam Integer[] ids, @RequestParam Integer sellStatus) {
        adminProductService.batchUpdateProductStatus(ids, sellStatus);
        return GenericReturnApiObject.success(null);
    }

    /**
     *  后台商品列表
     */
    @AdminRequired
    @Operation(summary = "后台商品列表")
    @GetMapping("/getProductList")
    public GenericReturnApiObject getProductList(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) {
        return GenericReturnApiObject.success(adminProductService.getProductList(pageNum, pageSize));
    }
}